/******************************************************************************* * Copyright (c) Gil Barash - chookapp@yahoo.com * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Gil Barash - initial API and implementation *******************************************************************************/ package com.chookapp.org.bracketeer.jdt; import java.util.EmptyStackException; import java.util.Iterator; import java.util.List; import java.util.Stack; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BreakStatement; import org.eclipse.jdt.core.dom.ContinueStatement; import org.eclipse.jdt.core.dom.DoStatement; import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.SwitchCase; import org.eclipse.jdt.core.dom.SwitchStatement; import org.eclipse.jdt.core.dom.SynchronizedStatement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeParameter; import org.eclipse.jdt.core.dom.WhileStatement; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import com.chookapp.org.bracketeer.common.BracketsPair; import com.chookapp.org.bracketeer.common.Hint; import com.chookapp.org.bracketeer.common.IBracketeerProcessingContainer; import com.chookapp.org.bracketeer.common.IHintConfiguration; import com.chookapp.org.bracketeer.common.MutableBool; public class ClosingBracketHintVisitor extends ASTVisitor { class ScopeInfo { public String _str; public int _offset; public Statement _statement; public ScopeInfo(String str, int offset, Statement statement) { _str = str; _offset = offset; _statement = statement; } } public class ScopeTraceException extends Exception { private static final long serialVersionUID = 6297837237586982280L; public ScopeTraceException(String message) { super(message); } } MutableBool _cancelProcessing; IBracketeerProcessingContainer _container; IHintConfiguration _hintConf; IDocument _doc; Stack<ScopeInfo> _scopeStack; public ClosingBracketHintVisitor(IBracketeerProcessingContainer container, IDocument doc, MutableBool cancelProcessing, IHintConfiguration hintConf) { _cancelProcessing = cancelProcessing; _container = container; _doc = doc; _hintConf = hintConf; _scopeStack = new Stack<ClosingBracketHintVisitor.ScopeInfo>(); } private void removeFromStack(Statement node) { try { ScopeInfo scope; if( node instanceof SwitchStatement ) { scope = _scopeStack.peek(); if(scope._statement instanceof SwitchCase) { _scopeStack.pop(); } } scope = _scopeStack.pop(); if(!scope._statement.getClass().equals(node.getClass())) { if(Activator.DEBUG) { Activator.log("Lost track of scope. Expected:" + scope._statement + //$NON-NLS-1$ " but was:" + node); //$NON-NLS-1$ } } } catch(EmptyStackException e) { if(Activator.DEBUG) Activator.log(e); } } private void addBrackets(ParameterizedType node) throws BadLocationException { @SuppressWarnings("unchecked") List<Type> args = node.typeArguments(); if(args.isEmpty()) return; Type type = args.get(0); int startLoc = type.getStartPosition() - 1; type = args.get(args.size()-1); int endLoc = type.getStartPosition() + type.getLength(); _container.add(new BracketsPair(startLoc, '<', endLoc, '>')); } private void addBrackets(List<TypeParameter> typeParameters) throws BadLocationException { if(typeParameters == null || typeParameters.isEmpty()) return; TypeParameter type = typeParameters.get(0); int startLoc = type.getStartPosition() - 1; type = typeParameters.get(typeParameters.size()-1); int endLoc = type.getStartPosition() + type.getLength(); _container.add(new BracketsPair(startLoc, '<', endLoc, '>')); } @Override public boolean visit(ParameterizedType node) { try { addBrackets(node); } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public boolean visit(SwitchCase node) { /* TODO: specific params: don't show the switch part (only the case argument) */ try { ScopeInfo scope = _scopeStack.peek(); if( !(scope._statement instanceof SwitchStatement) ) { if(!(scope._statement instanceof SwitchCase)) { throw new ScopeTraceException("Lost track of stack (in case), found:" + scope._statement); //$NON-NLS-1$ } _scopeStack.pop(); scope = _scopeStack.peek(); } if( !(scope._statement instanceof SwitchStatement) ) { throw new ScopeTraceException("Lost track of stack (in case2), found:" + scope._statement); //$NON-NLS-1$ } String hint = ""; //$NON-NLS-1$ if( node.isDefault() ) { hint = "default"; //$NON-NLS-1$ } else { hint = "case: " + node.getExpression(); //$NON-NLS-1$ } int startLoc = node.getStartPosition(); _scopeStack.push(new ScopeInfo(scope._str + " - " + hint, startLoc, node)); //$NON-NLS-1$ } catch(ScopeTraceException e) { if(Activator.DEBUG) Activator.log(e); } catch(EmptyStackException e) { if(Activator.DEBUG) Activator.log(e); } return shouldContinue(); } @Override public boolean visit(DoStatement node) { String hint = GetNodeText(node.getExpression()); int startLoc = node.getStartPosition(); hint = "do_while( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$ _scopeStack.push(new ScopeInfo(hint, startLoc, node)); return shouldContinue(); } @Override public void endVisit(DoStatement node) { removeFromStack(node); super.endVisit(node); } @Override public boolean visit(BreakStatement node) { return visitBreak(node); } @Override public boolean visit(ContinueStatement node) { return visitBreak(node); } private boolean visitBreak(Statement node) { try { if(_scopeStack.isEmpty()) throw new ScopeTraceException("break without scope: " + node); //$NON-NLS-1$ ScopeInfo scope = _scopeStack.peek(); if(node instanceof BreakStatement) { // ignoring break with labels on them if( ((BreakStatement)node).getLabel() != null ) return shouldContinue(); } String hintType; if( scope._statement instanceof ForStatement ) hintType = "break-for"; //$NON-NLS-1$ else if( scope._statement instanceof EnhancedForStatement ) hintType = "break-foreach"; //$NON-NLS-1$ else if( scope._statement instanceof WhileStatement ) hintType = "break-while"; //$NON-NLS-1$ else if( scope._statement instanceof DoStatement ) hintType = "break-do"; //$NON-NLS-1$ else if( scope._statement instanceof SwitchCase ) hintType = "break-case"; //$NON-NLS-1$ else throw new ScopeTraceException("Unexpect scope ("+scope._statement+") on break/continue:" + node); //$NON-NLS-1$ //$NON-NLS-2$ int endLoc = node.getStartPosition() + node.getLength() - 1; _container.add(new Hint(hintType, scope._offset, endLoc, scope._str)); } catch(ScopeTraceException e) { if(Activator.DEBUG) Activator.log(e); } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @SuppressWarnings("unchecked") @Override public boolean visit(TypeDeclaration node) { String hint = node.getName().getIdentifier(); int startLoc = node.getName().getStartPosition(); int endLoc = node.getStartPosition() + node.getLength() - 1; try { _container.add(new Hint("type", startLoc, endLoc, hint)); //$NON-NLS-1$ addBrackets(node.typeParameters()); } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public boolean visit(MethodDeclaration node) { // starting a function empties the stack... (which should already be empty on good flow) _scopeStack.clear(); StringBuffer hint = new StringBuffer(); hint.append(node.getName().getIdentifier()); /* TODO: specific params: exclude function parameters (show only the name) */ hint.append("( "); //$NON-NLS-1$ for (@SuppressWarnings("rawtypes") Iterator iterator = node.parameters().iterator(); iterator.hasNext();) { SingleVariableDeclaration param = (SingleVariableDeclaration) iterator.next(); hint.append(param.getName()); if( iterator.hasNext() ) hint.append(", "); //$NON-NLS-1$ } hint.append(" )"); //$NON-NLS-1$ int startLoc = node.getName().getStartPosition(); int endLoc = node.getStartPosition() + node.getLength() - 1; try { _container.add(new Hint("function", startLoc, endLoc, hint.toString())); //$NON-NLS-1$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public boolean visit(ForStatement node) { /* TODO: specific params: show also initializer && increment expressions */ String hint = GetNodeText(node.getExpression()); hint = "for( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$ int startLoc = node.getStartPosition(); int endLoc = startLoc + node.getLength() - 1; _scopeStack.push(new ScopeInfo(hint, startLoc, node)); try { _container.add(new Hint("for", startLoc, endLoc, hint)); //$NON-NLS-1$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public void endVisit(ForStatement node) { removeFromStack(node); super.endVisit(node); } @Override public boolean visit(EnhancedForStatement node) { /* TODO: specific params: put 2 checkboxes: the var name & the collection */ String hint = GetNodeText(node.getExpression()); int startLoc = node.getStartPosition(); int endLoc = startLoc + node.getLength() - 1; hint = "foreach( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$ _scopeStack.push(new ScopeInfo(hint, startLoc, node)); try { _container.add(new Hint("foreach", startLoc, endLoc, hint)); //$NON-NLS-1$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public void endVisit(EnhancedForStatement node) { removeFromStack(node); super.endVisit(node); } @Override public boolean visit(SwitchStatement node) { String hint = GetNodeText(node.getExpression()); int startLoc = node.getStartPosition(); int endLoc = startLoc + node.getLength() - 1; hint = "switch( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$ _scopeStack.push(new ScopeInfo(hint, startLoc, node)); try { _container.add(new Hint("switch", startLoc, endLoc, hint)); //$NON-NLS-1$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public void endVisit(SwitchStatement node) { removeFromStack(node); super.endVisit(node); } @Override public boolean visit(WhileStatement node) { String hint = GetNodeText(node.getExpression()); int startLoc = node.getStartPosition(); int endLoc = startLoc + node.getLength() - 1; hint = "while( "+hint+" )"; //$NON-NLS-1$ //$NON-NLS-2$ _scopeStack.push(new ScopeInfo(hint, startLoc, node)); try { _container.add(new Hint("while", startLoc, endLoc, hint)); //$NON-NLS-1$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public void endVisit(WhileStatement node) { removeFromStack(node); super.endVisit(node); } @Override public boolean visit(SynchronizedStatement node) { String hint = GetNodeText(node.getExpression()); int startLoc = node.getStartPosition(); int endLoc = startLoc + node.getLength() - 1; try { _container.add(new Hint("synchronized", startLoc, endLoc, "synchronized( "+hint+" )")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } @Override public boolean visit(IfStatement node) { Statement thenStmt = node.getThenStatement(); Statement elseStmt = node.getElseStatement(); String hint = GetNodeText(node.getExpression()); boolean showIfHint = (elseStmt == null); int endLoc = -1; try { if( !showIfHint ) { if(_doc.getLineOfOffset(elseStmt.getStartPosition()) != _doc.getLineOfOffset(thenStmt.getStartPosition() + thenStmt.getLength())) { showIfHint = true; } // if the else looks like this "} else {", then show the hint on the "{" if(!showIfHint && !(elseStmt instanceof IfStatement)) { endLoc = elseStmt.getStartPosition(); showIfHint = true; } } if( showIfHint && !(thenStmt instanceof Block)) showIfHint = false; if( showIfHint ) { if( endLoc == -1 ) endLoc = thenStmt.getStartPosition() + thenStmt.getLength()-1; int startLoc = node.getStartPosition(); _container.add(new Hint("if", startLoc, endLoc, "if( "+hint+" )")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } if( elseStmt != null && !(elseStmt instanceof IfStatement) && (elseStmt instanceof Block)) { endLoc = elseStmt.getStartPosition() + elseStmt.getLength()-1; int startLoc = elseStmt.getStartPosition(); _container.add(new Hint("if", startLoc, endLoc, "else_of_if( "+hint+" )")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } catch (BadLocationException e) { _cancelProcessing.set(true); } return shouldContinue(); } private String GetNodeText(ASTNode node) { if( node == null ) return ""; //$NON-NLS-1$ try { return _doc.get(node.getStartPosition(), node.getLength()); } catch (BadLocationException e) { return ""; //$NON-NLS-1$ } } private boolean shouldContinue() { return !_cancelProcessing.get(); } }